
class HttpService {
    var apiToken = ""
    var apiRefreshToken = ""
    var apiUploadUrl = ""
    var userPortraitDirectory = ""
    var groupPortraitDirectory = ""
    var fileUrlPrefix = ""
    var tempDirectory = ""
    var urlBase = ""
    
    init(){
        let defaults = UserDefaults.standard
        urlBase = defaults.string(forKey: "ospn_api_url_base") ?? ""
        apiToken = defaults.string(forKey: "ospn_api_token") ?? ""
        apiRefreshToken = defaults.string(forKey: "ospn_api_refresh_token") ?? ""
        apiUploadUrl = defaults.string(forKey: "ospn_api_upload_url") ?? ""
        userPortraitDirectory = defaults.string(forKey: "ospn_api_user_portrait_directory") ?? ""
        groupPortraitDirectory = defaults.string(forKey: "ospn_api_groupPortraitDirectory") ?? ""
        fileUrlPrefix = defaults.string(forKey: "ospn_api_fileUrlPrefix") ?? ""
        tempDirectory = defaults.string(forKey: "ospn_api_tempDirectory") ?? ""
    }
    func setBaseUrl(_ url:String){
        urlBase = url
        UserDefaults.standard.set(url, forKey: "ospn_api_url_base")
    }
    func setConfig(_ config:[String:Any]){
        apiToken = config["token"] as? String ?? ""
        apiRefreshToken = config["refreshToken"] as? String ?? ""
        let loginConfig = config["loginConfig"] as? [String:Any] ?? [:]
        apiUploadUrl = loginConfig["apiUploadUrl"] as? String ?? ""
        userPortraitDirectory = loginConfig["userPortraitDirectory"] as? String ?? ""
        groupPortraitDirectory = loginConfig["groupPortraitDirectory"] as? String ?? ""
        fileUrlPrefix = loginConfig["fileUrlPrefix"] as? String ?? ""
        tempDirectory = loginConfig["tempDirectory"] as? String ?? ""

        let defaults = UserDefaults.standard
        defaults.set(apiToken, forKey: "ospn_api_token")
        defaults.set(apiRefreshToken, forKey: "ospn_api_refresh_token")
        defaults.set(apiUploadUrl, forKey: "ospn_api_upload_url")
        defaults.set(userPortraitDirectory, forKey: "ospn_api_user_portrait_directory")
        defaults.set(groupPortraitDirectory, forKey: "ospn_api_groupPortraitDirectory")
        defaults.set(fileUrlPrefix, forKey: "ospn_api_fileUrlPrefix")
        defaults.set(tempDirectory, forKey: "ospn_api_tempDirectory")
    }
    func doPost(_ urls: String, _ body: String, _ method: String = "POST", _ head:[String:Any]? = nil) async -> [String:Any]? {
        let (res,err) = await doPostWithError(urls, body, method, head)

        if err != nil{
            return nil
        }
        
        if let code = res!["code"] as? Int {
            if code == 403 {
//                geventService.post(.kTokenError, "")
            }
                
        }
        
        return res
    }
    func doPostWithError(_ urls: String, _ body: String, _ method: String = "POST", _ head:[String:Any]? = nil) async -> ([String:Any]?,OspnError?) {
        guard let url = URL(string: urlBase+urls) else {return (nil,OspnError.Config)}
        var request = URLRequest(url: url, timeoutInterval: 15)
        request.httpMethod = method
        if let head = head {
            for (k,v) in head {
                request.setValue(v as? String, forHTTPHeaderField: k)
            }
        }else{
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        request.setValue("Token "+apiToken, forHTTPHeaderField: "Authorization")
        do {
            let (data, response) = try await URLSession.shared.upload(for: request, from: body.data(using: .utf8)!)
            
            
            
            guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
                print("Request failed: \(response)")
                return (nil,OspnError.Response)
            }
            guard let res = OsnUtils.toJson(data) else {return (nil, OspnError.Encode)}
            return (res, nil)
        } catch {
            print("http request error: \(error)")
            return (nil,OspnError.Custom(msg: "\(error)"))
        }
    }
    func downloadFile(_ url:String, _ path:String) async -> OspnError? {
        guard let url = URL(string: url) else {
            return OspnError.Custom(msg: "Invalid URL")
        }
        let fileUrl = URL(fileURLWithPath: path)
        do {
            let (data, response) = try await URLSession.shared.data(from: url)
            if let httpResponse = response as? HTTPURLResponse,
               (200...299).contains(httpResponse.statusCode) {
                try data.write(to: fileUrl)
            } else {
                return OspnError.Custom(msg: "Invalid Response")
            }
        } catch {
            print("\(error)")
            return OspnError.Custom(msg: error.localizedDescription)
        }
        return nil
    }
    func uploadUserPortrait(_ mediaData: Data, _ onProgress: ((Int, Int) -> Void)? = nil) async -> (String?, OspnError?) {
        let fileName = "\(OsnUtils.getTimeStamp()).jpg"
        return await uploadData(fileName, mediaData, OsnUploadType.userPortrait, onProgress)
    }
    func uploadGroupPortrait(_ mediaData: Data, _ onProgress: ((Int, Int) -> Void)? = nil) async -> (String?, OspnError?) {
        let fileName = "\(OsnUtils.getTimeStamp()).jpg"
        return await uploadData(fileName, mediaData, OsnUploadType.groupPortrait, onProgress)
    }
    func uploadMedia(_ mediaData: Data, _ onProgress: ((Int, Int) -> Void)? = nil) async -> (String?, OspnError?) {
        let fileName = "\(OsnUtils.getTimeStamp()).jpg"
        return await uploadData(fileName, mediaData, OsnUploadType.temp, onProgress)
    }
    private func uploadWithProgress(_ request:URLRequest, _ body:Data, _ onProgress: ((Int, Int) -> Void)?) async -> (Data?, OspnError?) {
        do{
            return try await withCheckedThrowingContinuation { continuation in
                let task = URLSession.shared.uploadTask(with: request, from: body) { data, response, error in
                    if let error = error {
                        continuation.resume(throwing: error)
                    } else if let data = data, let response = response {
                        if let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) {
                            continuation.resume(returning: (data, nil))
                        }else{
                            continuation.resume(throwing: OspnError.Response)
                        }
                    } else {
                        continuation.resume(throwing: URLError(.badServerResponse))
                    }
                }
                Task {
                    while(task.state == .running){
                        onProgress?(Int(task.countOfBytesSent), Int(body.count))
                        sleep(1)
                    }
                }
                task.resume()
            }
        }catch{
            return (nil, OspnError.Custom(msg: error.localizedDescription))
        }
    }
    func uploadFile(_ fileName:String, _ filePath: String, _ type: OsnUploadType, _ onProgress: ((Int, Int) -> Void)?) async -> (String?, OspnError?) {
        guard let fileData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
            return (nil, OspnError.Read)
        }
        return await uploadData(fileName, fileData, type, onProgress)
    }
    func uploadData(_ fileName:String, _ fileData: Data, _ type: OsnUploadType, _ onProgress: ((Int, Int) -> Void)?) async -> (String?, OspnError?) {
        guard let url = URL(string: apiUploadUrl) else {return (nil, OspnError.Config)}
        var request = URLRequest(url: url, timeoutInterval: 15)
        request.httpMethod = "POST"
        
        let ts = Date().timeIntervalSince1970 * 1000
        let boundary = "--------------------------\(Int(ts))"
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        
        let body = NSMutableData()
        var path:String
        switch type {
        case .temp: path = tempDirectory
        case .userPortrait: path = userPortraitDirectory
        case .groupPortrait: path = groupPortraitDirectory
        }
        path += "\n"
        body.append("--\(boundary)\r\nContent-Disposition: form-data; name=\"path\"\r\n\r\n".data(using: .utf8)!)
        body.append(path.data(using: .utf8)!)
        body.append("\r\n".data(using: .utf8)!)
        
        let useOriginalFilename = "true\n"
        body.append("--\(boundary)\r\nContent-Disposition: form-data; name=\"useOriginalFilename\"\r\n\r\n".data(using: .utf8)!)
        body.append(useOriginalFilename.data(using: .utf8)!)
        body.append("\r\n".data(using: .utf8)!)
        
        let filePair = "--\(boundary)\r\nContent-Disposition: form-data; name=\"file\"; filename=\"\(fileName)\"\r\nContent-Type: application/octet-stream\r\n\r\n"
        body.append(filePair.data(using: .utf8)!)
        body.append(fileData)
        body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
        
        //request.httpBody = body as Data?
        request.setValue("\(body.length)", forHTTPHeaderField: "Content-Length")
        
        var resUrl:String? = nil
        var resErr:OspnError? = nil
        let (data, err) = await uploadWithProgress(request, body as Data, onProgress)
        if let err = err {
            resErr = err
        } else if let data = data {
            if let content = OsnUtils.fromData(data: data){
                if let res = getResult(content) {
                    resUrl = res["url"] as? String ?? ""
                }else{
                    resErr = getError(content)
                }
            }else{
                resErr = OspnError.Response
            }
        }
        return (resUrl, resErr)
    }
    func getError(_ data:[String:Any])->OspnError{
        return OspnError.Custom(msg: data["msg"] as? String ?? "service failure")
    }
    func getResult(_ data:[String:Any]) -> [String:Any]?{
        if isOk(data){
            return data["data"] as? [String:Any]
        }
        return nil
    }
    func isOk(_ data:[String:Any]) ->Bool{
        return data["code"] as? Int ?? -1 == 0
    }
    func getResultWithError(_ data:[String:Any])->([String:Any]?,OspnError?){
        if isOk(data), let data = data["data"] as? [String:Any]{
            return (data, nil)
        }
        return (nil, OspnError.Custom(msg: data["msg"] as? String ?? "service failure"))
    }
    func getCode(_ account:String,_ type:Int) async -> ([String:Any]?,OspnError?) {
        let json:[String:Any] = ["type": type, "target": account]
        let (_, err) = await doPostWithError("/user-service/api/user/send-code", OsnUtils.toString(json) ?? "")
        if let err = err {
            return (nil, err)
        }
        return (nil,nil)
    }
    func register(_ user:String, _ pass:String, _ code:String) async -> ([String:Any]?,OspnError?) {
        let json: [String: Any] = [
            "type": 2,
            "mobile": user,
            "password": EcUtils.osnHash(data: pass.data(using: .utf8)!),
            "nickname": user,
            "project": "DEFAULT",
            "portrait": "",
            "code": code
        ]
        let (res,err) = await doPostWithError("/user-service/api/user/register", OsnUtils.toString(json) ?? "")
        if err != nil{
            return (res, err)
        }
        return getResultWithError(res!)
    }
    func loginWithCode(_ mobile:String, _ code:String) async -> ([String:Any]?, OspnError?){
        let json:[String:Any] = [
            "type": 2,
            "mobile": mobile,
            "code": code
        ]
        let (res, err) = await doPostWithError("/user-service/api/user/login", OsnUtils.toString(json) ?? "")
        if let err = err {
            return (nil, err)
        }
        return getResultWithError(res!)
    }
    func loginWithPassword(_ user:String, _ pass:String) async -> ([String:Any]?, OspnError?) {
        let json: [String:Any] = [
            "type": 1,
            "username": user,
            "password": EcUtils.osnHash(data: pass.data(using: .utf8) ?? Data())
        ]
        let (res, err) = await doPostWithError("/user-service/api/user/login", OsnUtils.toString(json) ?? "")
        if let err = err {
            return (nil, err)
        }
        return getResultWithError(res!)
    }
    func createGroup(_ name:String, _ portrait:String, _ userList:[String]) async ->([String:Any]?,OspnError?) {
        let data:[String:Any] = ["name":name, "portrait":portrait, "userList":userList]
        let (res, err) = await doPostWithError("/user-service/api/group/create-group", OsnUtils.toString(data) ?? "")
        if let err = err {
            return (nil, err)
        }
        return getResultWithError(res!)
    }
    func deleteGroup(_ groupId:String) async ->Bool {
        guard let res = await doPost("/user-service/api/group/delete-group?osnId="+groupId, "", "DELETE") else {return false}
        return isOk(res)
    }
    func listGroup() async ->([NSDictionary]?,OspnError?){
        guard let res = await doPost("/user-service/api/group/list-group?current=1&size=-1", "", "GET") else {
            return (nil,OspnError.Response)
        }
        if let data = getResult(res) {
            if let page = data["page"] as? [String:Any]{
//               return (page["records"] as? [String] ?? [], nil) groupOsnId
                if let records = page["records"] as? [NSDictionary]{
                    return (records,nil)
                }
            }
        }
        return (nil, getError(res))
    }

    func addGroupMemberMax(_ groupOsnId:String, _ code:String) async ->[String:Any]? {
        let data:[String:Any] = ["code":code,"type":2000, "param":["groupOsnId":groupOsnId]] as [String : Any]
        return await doPost("/user-service/api/activation-code/use", OsnUtils.toString(data) ?? "")
    }
    
    func delectAccount() async ->[String:Any]? {
        return await doPost("/user-service/api/user/delete-self", "")
    }

    
    func forgotPassword(_ target:String, _ pass:String, _ code:String) async -> ([String:Any]?,OspnError?) {
        let json: [String: Any] = [
            "target": target,
            "newPassword": EcUtils.osnHash(data: pass.data(using: .utf8)!),
            "code": code
        ]
        let (res,err) = await doPostWithError("/user-service/api/user/change-password-by-code", OsnUtils.toString(json) ?? "")
        if err != nil{
            return (res, err)
        }
        return (nil,nil)
    }
    
    func refreshToken(_ refreshToken:String) async -> ([String:Any]?,OspnError?) {
        let json: [String: Any] = [
            "refreshToken": refreshToken
        ]
        let (res,err) = await doPostWithError("/user-service/api/user/refresh-token", OsnUtils.toString(json) ?? "")
        if err != nil{
            return (res, err)
        }
        return getResultWithError(res!)
    }
    
    func requestDeviceToken(_ deviceId:String) async -> ([String:Any]?,OspnError?) {
        let json: [String: Any] = [
            "vendor": 10,
            "deviceId": deviceId
        ]
        let (res,err) = await doPostWithError("/user-service/api/user/set-user-device", OsnUtils.toString(json) ?? "")
        if err != nil{
            return (res, err)
        }
        return getResultWithError(res!)
    }
    
    func requestNearUsers(_ tel:String) async ->([String]?,OspnError?){
        guard let res = await doPost("/user-service/api/user/get-recommended-osn-ids", "", "GET") else {
            return (nil,OspnError.Response)
        }
        if let data = getResult(res) {
            if let records = data["recommendedOsnIds"] as? [String]{
                return (records,nil)
            }
        }
        return (nil, getError(res))
    }
    
    func requestUploadGroupMember(_ code:String,_ groupId:String) async -> ([String:Any]?,OspnError?) {
        let json: [String: Any] = [
            "code": code,
            "type": 2000,
            "param": ["groupOsnId":groupId]
        ]
        let (res,err) = await doPostWithError("/user-service/api/activation-code/use", OsnUtils.toString(json) ?? "")
        if let data = getResult(res!) {
            if let result = data["result"] as? [String:Any]{
                return (result,nil)
            }
        }
        return getResultWithError(res!)
    }
    
}
let ghttpService = HttpService()
